/*******************************************
 * Copyright 2007 Aphrodite Framework for javaME .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * Email : owner@aphroditeme.dev.java.net
 * Contributors:
 *     Kent Elton - initial API and implementation
 *******************************************/

package com.aphrodite.image;

/**
 * Image filters similar to kernel's in j2se
 * 
 * @author kentelt
 * 
 */
public class ImageFilters {
	/**
	 * Performs a convolution of an image with a given matrix.
	 * 
	 * @param filterMatrix
	 *            a matrix, which should have odd rows an colums (not
	 *            neccessarily a square). The matrix is used for a 2-dimensional
	 *            convolution. Negative values are possible.
	 * 
	 * @param brightness
	 *            you can vary the brightness of the image measured in percent.
	 *            Note that the algorithm tries to keep the original brightness
	 *            as far as is possible.
	 * 
	 * @param argbData
	 *            the image (RGB+transparency)
	 * 
	 * @param width
	 *            of the given Image
	 * 
	 * @param height
	 *            of the given Image Be aware that the computation time depends
	 *            on the size of the matrix.
	 * @throws IllegalArgumentException
	 *             if the filter matrix length is an even number
	 */
	private final static void applyFilter(byte[][] filterMatrix, int brightness, int[] argbData, int width, int height) {
		// ############ tested by $t3p#3n on 29-july-08 #################//
		int COLOR_BIT_MASK = 0x000000FF;
		// check whether the matrix is ok
		if (filterMatrix.length % 2 != 1 || filterMatrix[0].length % 2 != 1) {
			throw new IllegalArgumentException();
		}

		int fhRadius = filterMatrix.length / 2 + 1;
		int fwRadius = filterMatrix[0].length / 2 + 1;
		int currentPixel = 0;
		int newTran, newRed, newGreen, newBlue;

		// compute the brightness
		int divisor = 0;
		for (int fCol, fRow = 0; fRow < filterMatrix.length; fRow++) {
			for (fCol = 0; fCol < filterMatrix[0].length; fCol++) {
				divisor += filterMatrix[fRow][fCol];
			}
		}
		// TODO: if (divisor==0), because of negativ matrixvalues
		if (divisor == 0) {
			return; // no brightness
		}

		// copy the neccessary imagedata into a small buffer
		int[] tmpRect = new int[width * (filterMatrix.length)];
		System.arraycopy(argbData, 0, tmpRect, 0, width * (filterMatrix.length));

		for (int fCol, fRow, col, row = fhRadius - 1; row + fhRadius < height + 1; row++) {
			for (col = fwRadius - 1; col + fwRadius < width + 1; col++) {

				// perform the convolution
				newTran = 0;
				newRed = 0;
				newGreen = 0;
				newBlue = 0;

				for (fRow = 0; fRow < filterMatrix.length; fRow++) {

					for (fCol = 0; fCol < filterMatrix[0].length; fCol++) {

						// take the Data from the little buffer and skale the
						// color
						currentPixel = tmpRect[fRow * width + col + fCol - fwRadius + 1];
						if (((currentPixel >>> 24) & COLOR_BIT_MASK) != 0) {
							newTran += filterMatrix[fRow][fCol] * ((currentPixel >>> 24) & COLOR_BIT_MASK);
							newRed += filterMatrix[fRow][fCol] * ((currentPixel >>> 16) & COLOR_BIT_MASK);
							newGreen += filterMatrix[fRow][fCol] * ((currentPixel >>> 8) & COLOR_BIT_MASK);
							newBlue += filterMatrix[fRow][fCol] * (currentPixel & COLOR_BIT_MASK);
						}

					}
				}

				// calculate the color
				newTran = newTran * brightness / 100 / divisor;
				newRed = newRed * brightness / 100 / divisor;
				newGreen = newGreen * brightness / 100 / divisor;
				newBlue = newBlue * brightness / 100 / divisor;

				newTran = Math.max(0, Math.min(255, newTran));
				newRed = Math.max(0, Math.min(255, newRed));
				newGreen = Math.max(0, Math.min(255, newGreen));
				newBlue = Math.max(0, Math.min(255, newBlue));
				argbData[(row) * width + col] = (newTran << 24 | newRed << 16 | newGreen << 8 | newBlue);

			}

			// shift the buffer if we are not near the end
			if (row + fhRadius != height) {
				System.arraycopy(tmpRect, width, tmpRect, 0, width * (filterMatrix.length - 1)); // shift
				// it
				// back
				System.arraycopy(argbData, width * (row + fhRadius), tmpRect, width * (filterMatrix.length - 1), width); // add
				// new
				// data
			}
		}
		// return Image.createRGBImage(argbData, width, height, true);
	}

	/**
	 * Apply emboss filter to the given image data
	 * <p>
	 * <table border="0">
	 * <tr>
	 * <td><b>Before</b></td>
	 * <td><b>After</b></td>
	 * </tr>
	 * <tr>
	 * <td>
	 * <img src="../../../resources/before.png"></td>
	 * <td><img src="../../../resources/emboss.png"></td>
	 * </tr>
	 * </table>
	 * </p>
	 * 
	 * @param argbData
	 *            of the image
	 * @param width
	 *            of the image
	 * @param height
	 *            of the image
	 */
	public static void embossFilter(int[] argbData, int width, int height) {
		byte[][] filter = { { -2, 0, 0 }, { 0, 1, 0 }, { 0, 0, 2 } };
		applyFilter(filter, 100, argbData, width, height);
	}

	/**
	 * Erodes pixels of a grayscale image
	 * <p>
	 * The following is an example of eroded pixel filter with
	 * <code>inversion</code> set to <code>false</code>
	 * <table border="0">
	 * <tr>
	 * <td><b>Before</b></td>
	 * <td><b>After</b></td>
	 * </tr>
	 * <tr>
	 * <td>
	 * <img src="../../../resources/before.png"></td>
	 * <td><img src="../../../resources/eroded.png"></td>
	 * </tr>
	 * </table>
	 * </p>
	 * 
	 * @param pixels
	 * @param width
	 * @param height
	 * @param isInverted
	 * @return
	 */
	public static int[] erodedPixelFilter(int[] pixels, int width, int height, boolean isInverted) {
		int currIdx = 0;
		int maxIdx = pixels.length;
		int[] out = new int[maxIdx];

		if (isInverted) {
			// dilation (grow light areas)
			while (currIdx < maxIdx) {
				int currRowIdx = currIdx;
				int maxRowIdx = currIdx + width;
				while (currIdx < maxRowIdx) {
					int colOrig, colOut;
					colOrig = colOut = pixels[currIdx];
					int idxLeft = currIdx - 1;
					int idxRight = currIdx + 1;
					int idxUp = currIdx - width;
					int idxDown = currIdx + width;
					if (idxLeft < currRowIdx)
						idxLeft = currIdx;
					if (idxRight >= maxRowIdx)
						idxRight = currIdx;
					if (idxUp < 0)
						idxUp = 0;
					if (idxDown >= maxIdx)
						idxDown = currIdx;

					int colUp = pixels[idxUp];
					int colLeft = pixels[idxLeft];
					int colDown = pixels[idxDown];
					int colRight = pixels[idxRight];

					// compute luminance
					int currLum = colOrig & 0xff;
					int lumLeft = colLeft & 0xff;
					int lumRight = colRight & 0xff;
					int lumUp = colUp & 0xff;
					int lumDown = colDown & 0xff;

					if (lumLeft > currLum) {
						colOut = colLeft;
						currLum = lumLeft;
					}
					if (lumRight > currLum) {
						colOut = colRight;
						currLum = lumRight;
					}
					if (lumUp > currLum) {
						colOut = colUp;
						currLum = lumUp;
					}
					if (lumDown > currLum) {
						colOut = colDown;
						currLum = lumDown;
					}
					out[currIdx++] = colOut;
				}
			}
		} else {
			// erode (grow dark areas)
			while (currIdx < maxIdx) {
				int currRowIdx = currIdx;
				int maxRowIdx = currIdx + width;
				while (currIdx < maxRowIdx) {
					int colOrig, colOut;
					colOrig = colOut = pixels[currIdx];
					int idxLeft = currIdx - 1;
					int idxRight = currIdx + 1;
					int idxUp = currIdx - width;
					int idxDown = currIdx + width;
					if (idxLeft < currRowIdx)
						idxLeft = currIdx;
					if (idxRight >= maxRowIdx)
						idxRight = currIdx;
					if (idxUp < 0)
						idxUp = 0;
					if (idxDown >= maxIdx)
						idxDown = currIdx;

					int colUp = pixels[idxUp];
					int colLeft = pixels[idxLeft];
					int colDown = pixels[idxDown];
					int colRight = pixels[idxRight];

					// compute luminance
					int currLum = colOrig & 0xff;
					int lumLeft = colLeft & 0xff;
					int lumRight = colRight & 0xff;
					int lumUp = colUp & 0xff;
					int lumDown = colDown & 0xff;

					if (lumLeft < currLum) {
						colOut = colLeft;
						currLum = lumLeft;
					}
					if (lumRight < currLum) {
						colOut = colRight;
						currLum = lumRight;
					}
					if (lumUp < currLum) {
						colOut = colUp;
						currLum = lumUp;
					}
					if (lumDown < currLum) {
						colOut = colDown;
						currLum = lumDown;
					}
					out[currIdx++] = colOut;
				}
			}
		}
		return out;
	}

	/**
	 * Apply Gaussian blur Filter to the given image data
	 * <p>
	 * <table border="0">
	 * <tr>
	 * <td><b>Before</b></td>
	 * <td><b>After</b></td>
	 * </tr>
	 * <tr>
	 * <td>
	 * <img src="../../../resources/before.png"></td>
	 * <td><img src="../../../resources/gaussian.png"></td>
	 * </tr>
	 * </table>
	 * </p>
	 * 
	 * @param argbData
	 *            of the image
	 * @param brightness
	 *            of the result. Optimum values are within 200
	 * @param width
	 *            of the image
	 * @param height
	 *            of the image
	 */
	public static void gaussianBlurFilter(int[] argbData, int brightness, int width, int height) {
		byte[][] filter = { { 1, 2, 1 }, { 2, 4, 2 }, { 1, 2, 1 } };
		applyFilter(filter, brightness, argbData, width, height);
	}

	/**
	 * Generates only the border pixels of the given image
	 * 
	 * @param argbData
	 *            of the image
	 * @param width
	 *            of the image
	 * @param height
	 *            of the image
	 */
	public static void imageRimFilter(int[] argbData, int width, int height) {
		byte[][] filter = { { 0, -1, 0 }, { -1, 5, -1 }, { 0, -1, 0 } };
		applyFilter(filter, 0, argbData, width, height);
	}

	// public static void blurFilter2(int[] argbData, int brightness, int width,
	// int height) {
	// byte[][] filter = { { 0, -1, 0 }, { -1, 4, -1 }, { 0, -1, 0 } };
	// applyFilter(filter, 100, argbData, width, height);
	// }

	/**
	 * Apply image blur filter on the given image data
	 * <p>
	 * <table border="0">
	 * 
	 * <tr>
	 * <td><b>Before</b></td>
	 * <td><b>After</b></td>
	 * </tr>
	 * <tr>
	 * <td>
	 * <img src="../../../resources/before.png"></td>
	 * <td><img src="../../../resources/blur.png"></td>
	 * </tr>
	 * </table>
	 * </p>
	 * 
	 * @param argbData
	 *            of the image
	 * 
	 * @param width
	 *            of the image
	 * @param height
	 *            of the image
	 */
	public static void simpleBlurFilter(int[] argbData, int width, int height) {
		byte[][] filter = { { -1, -1, -1 }, { -1, 0, -1 }, { -1, -1, -1 } };
		applyFilter(filter, 100, argbData, width, height);
	}

	private ImageFilters() {}

}
